#include <jni.h>
#include <string>
#include <unistd.h>
#include <android/log.h>
#include <android/native_window_jni.h>
#include "_ffmpeg_video_decoder.h"
#include "_ffmpeg_audio_decoder.h"

extern "C" {
//编码
#include "libavcodec/avcodec.h"
//封装格式处理
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
//像素处理
#include "libswscale/swscale.h"
#include "_opensl_helper.h"
}

#define  LOG_TAG    "aruba"
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

//ffmpeg上下文
AVFormatContext *formatContext;
const char *filePath;
//开始播放
bool is_playing;
//线程是否运行
bool is_running;
FFmpegVideoDecoder *videoDecoder = NULL;
FFmpegAudioDecoder *audioDecoder = NULL;
//底层绘制对象
ANativeWindow *aNativeWindow = NULL;

//获取帧数据线程
pthread_t pid;

//获取帧数据线程
void *getPacket(void *arg) {
    //注册FFmpeg中各大组件
    av_register_all();

    //网络视频需要加上这句代码
    avformat_network_init();

    //打开文件
    formatContext = avformat_alloc_context();
    if (avformat_open_input(&formatContext, filePath, NULL, NULL) != 0) {
        LOGE("打开失败");
        avformat_free_context(formatContext);
        is_playing = false;
        pthread_exit(0);
    }

    //将文件信息填充进AVFormatContext
    if (avformat_find_stream_info(formatContext, NULL) < 0) {
        LOGE("获取文件信息失败");
        avformat_free_context(formatContext);
        is_playing = false;
        pthread_exit(0);
    }

    //获取视频流的编解码器上下文
    AVCodecContext *videoCodecContext = NULL;
    AVCodecContext *audioCodecContext = NULL;
    int video_stream_idx = -1;
    int audio_stream_idx = -1;
    //一秒中分成几等分,即一秒中有多少帧
    AVRational video_time_base;
    AVRational audio_time_base;
    for (int i = 0; i < formatContext->nb_streams; ++i) {
        if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {//如果是视频流
            videoCodecContext = formatContext->streams[i]->codec;
            video_stream_idx = i;
            video_time_base = formatContext->streams[i]->time_base;
        } else if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {//如果是音频流
            audioCodecContext = formatContext->streams[i]->codec;
            audio_stream_idx = i;
            audio_time_base = formatContext->streams[i]->time_base;
        }
    }

    if (videoCodecContext == NULL || audioCodecContext == NULL) {
        LOGE("获取编解码器上下文失败");
        avformat_free_context(formatContext);
        is_playing = false;
        pthread_exit(0);
    }

    //根据编解码器上下文的id获取视频流解码器
    AVCodec *videoCodec = avcodec_find_decoder(videoCodecContext->codec_id);
    //复制一个解码器
    AVCodecContext *videoCodecContextClone = avcodec_alloc_context3(videoCodec);
    avcodec_copy_context(videoCodecContextClone, videoCodecContext);
    //打开解码器
    if (avcodec_open2(videoCodecContextClone, videoCodec, NULL) < 0) {
        LOGE("打开视频解码失败");
        avformat_free_context(formatContext);
        is_playing = false;
        pthread_exit(0);
    }

    //根据编解码器上下文的id获取音频流解码器


    AVCodec *audioCodec = avcodec_find_decoder(audioCodecContext->codec_id);
    //复制一个解码器
    AVCodecContext *audioCodecContextClone = avcodec_alloc_context3(audioCodec);
    avcodec_copy_context(audioCodecContextClone, audioCodecContext);
    //打开解码器
    if (avcodec_open2(audioCodecContextClone, audioCodec, NULL) < 0) {
        LOGE("打开音频解码失败");
        avformat_free_context(formatContext);
        is_playing = false;
        pthread_exit(0);
    }

    videoDecoder->stream_idx = video_stream_idx;
    videoDecoder->time_base = video_time_base;
    videoDecoder->setAvCodecContext(videoCodecContextClone);
    audioDecoder->stream_idx = audio_stream_idx;
    audioDecoder->time_base = audio_time_base;
    audioDecoder->setAvCodecContext(audioCodecContextClone);


    videoDecoder->play();
    audioDecoder->play();

    //存放压缩数据
    AVPacket *pkt = (AVPacket *) (av_malloc(sizeof(AVPacket)));
    av_init_packet(pkt);

    while (av_read_frame(formatContext, pkt) == 0 && is_running) {//读到每一帧的压缩数据存放在AVPacket
        if (videoDecoder && videoDecoder->isPlay &&
            pkt->stream_index == videoDecoder->stream_idx) {//说明是视频流
            videoDecoder->put(pkt);
        } else if (audioDecoder && audioDecoder->isPlay &&
                   pkt->stream_index == audioDecoder->stream_idx) {
            audioDecoder->put(pkt);
        }
        av_packet_unref(pkt);
    }

    //停止解码
    LOGE("停止解码");
    videoDecoder->stop();
    audioDecoder->stop();
    videoDecoder = NULL;
    audioDecoder = NULL;
    //释放
    LOGE("释放packet");
    av_packet_unref(pkt);
    av_free_packet(pkt);
    pkt = NULL;
    LOGE("释放ffmpeg上下文");
    avformat_free_context(formatContext);
    formatContext = NULL;

    is_playing = false;
    pthread_exit(0);
};

extern "C"
JNIEXPORT jint JNICALL
Java_com_aruba_ffmpegsyncapplication_FFmpegPlayView_render(JNIEnv *env, jobject instance,
                                                           jstring filePath_) {
    filePath = env->GetStringUTFChars(filePath_, 0);
    env->ReleaseStringUTFChars(filePath_, filePath);

    videoDecoder = new FFmpegVideoDecoder;
    if (aNativeWindow) {
        videoDecoder->initWindow(aNativeWindow);
    }
    audioDecoder = new FFmpegAudioDecoder;
    videoDecoder->audioDecoder = audioDecoder;

    //开始读每一帧
    is_playing = true;
    is_running = true;
    pthread_create(&pid, 0, getPacket, NULL);
    return 0;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_aruba_ffmpegsyncapplication_FFmpegPlayView_display(JNIEnv *env, jobject instance,
                                                            jobject surface) {
    if (aNativeWindow) {
        ANativeWindow_release(aNativeWindow);
    }

    //获取底层绘制对象
    aNativeWindow = ANativeWindow_fromSurface(env, surface);
    if (videoDecoder) {
        videoDecoder->initWindow(aNativeWindow);
    }
}

extern "C"
JNIEXPORT void JNICALL
Java_com_aruba_ffmpegsyncapplication_FFmpegPlayView_stopPlay(JNIEnv *env, jobject instance) {
    is_running = false;
    pthread_join(pid, 0);//等待真正的停止
}

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_aruba_ffmpegsyncapplication_FFmpegPlayView_isPlaying(JNIEnv *env, jobject instance) {
    return is_playing;
}